#ifndef BMD_ENGINE_H
#define BMD_ENGINE_H

#include <vector>
#include "stage.h"
#include <fstream>

using namespace std;

/*
The Engine class controls gameplay.

Its main constituents are:
stage		- Stage class
bombers		- vectors
bombs		- |
powerups	- |
explosions	- |

The engine's main functionalities are:
- initialization
- execute_time_step

Another important functionallity is:
- fresh id generation for new bombs, bombers
- bomb_explosion()

Basically:
Time steps are instances between which the game transitions.
Movement, bomb detonation, etc. all occur in discrete time steps.
*/
class Engine
{

private:
	Stage stage;


	int max(int a, int b)
	{
		if(a > b)
			return a;
		return b;
	}

	int create_bomb_id()
	{
		if(bombs.size() > 0)
			return max(bombs.back().get_id()+1,bombs.size());
		return 0;
	}
	/*
		adds a new bomb to the "bombs" vector with the given ID and location
	*/
	void create_bomb(	const Coordinates & bomb_coords, 
						const unsigned int & bomber_id, 
						const BombProperties & in_prop, 
						const int fresh_id)
	{
		Bomb new_bomb;

		new_bomb.set(fresh_id, bomber_id, in_prop); //ADD DURATION
		new_bomb.coords = bomb_coords;

		bombs.push_back(new_bomb);
	}

	int create_bomber_id()
	{
		if(bombers.size() > 0)
			return max(bombers.back().get_id()+1,bombers.size());
		return 0;
	}
	/*
		add a new bomber
	*/
	void create_bomber(const Coordinates & bomber_coords, 
					   const int fresh_id)
	{
		//int fresh_id = rand();//(unsigned int)bombers.size();
		Bomber new_bomber(fresh_id);

		new_bomber.coords = bomber_coords;

		bombers.push_back(new_bomber);
	}

	void create_powerup(PowerUp & in_powerup)
	{
		int fresh_id;
		if(powerups.size() > 0)
			fresh_id = max(powerups.back().id+1,powerups.size());
		else
			fresh_id = 0;
		in_powerup.id = fresh_id;
		powerups.push_back(in_powerup);
	}


	/*
	bomb_explosion:
		obtains a list of objects effected/created by an exploding bomb by
		calling stage.detonate_bomb (see).

		afterwards it looks up the effected objects and performs appropriate
		operations:
		bomber - removes the bomber, informing the VM.
		bomb - explodes the bomb

		'brick' destruction is performed in the stage.detonate_bomb call since 
		bricks only exist in the stage's tile system.
	*/
	void bomb_explosion(Bomb& bomb, vector<Bomber>::iterator start_bomber)
	{
		ExplosionResult temp_result;
		int owner_id;
		vector<int>::iterator x_iter;
		vector<Bomber>::iterator x_bomber;
		vector<Bomb>::iterator x_bomb;
		vector<PowerUp>::iterator c_powerup;

		//use bomb.gone - pack all this into a function in engine - this way no iterator messiness
		//
		//
		//

		bomb.gone = true;
		owner_id = bomb.get_owner_id();

		// MASON owner_id holds the ID of a detonated bomb's owner
		bomb.get_id();
		//bomb.get_id() is the ID of the detonated bomb
		//enjoy





		x_bomber = bombers.begin();
		while(x_bomber != bombers.end()) 
		{		
			if(x_bomber->get_id() == owner_id)
			{
					x_bomber->nbombs--;
					break; //ids are unique
			}
				
			x_bomber++;
		}


		temp_result = stage.detonate_bomb(bomb.coords, bomb.prop.power);
		
		
		
		//iterate through the bombers
		for(x_iter = temp_result.ef_bombers.begin();
			x_iter != temp_result.ef_bombers.end();
			x_iter++)
		{
			//this finds the next bomber in the list
			x_bomber = bombers.begin();
			while(x_bomber != bombers.end() && x_bomber->get_id() != *x_iter) {x_bomber++;}
			//add kill if on different teams - for now always
			//bombers[*(x_iter)].kill(); //bomber_id always corresponds to vector index
			//bombers[starter_id].score.kills++;
			if(x_bomber != bombers.end())
			{
				x_bomber->kill();
				//clear reservation data
				if(x_bomber->mstate.is_displaced())
					stage.bomber_InformTileClear(x_bomber->mstate.reserved(x_bomber->coords));

				/*
				For future implementation:
				if there was a scoring system in place, kills/deaths would 
				be recoreded here. Victory conditions could then be checked here, 
				or better yet at the end of the appropriate execute_time_step loop.
				*/

				x_bomber->get_id();
				// MASON use x_bomber->get_id() to get the ID for the bomber
				//now pass it to the VM
				x_bomber->get_team_id();
				//here's the team ID, do you want a loop to find its teammates?
			}
		}

		//iterate through the created powerups
		for(c_powerup = temp_result.created_powerups.begin();
			c_powerup < temp_result.created_powerups.end();
			c_powerup++)
		{
			create_powerup(*c_powerup);
			stage.place_powerup(*c_powerup);
		}

		
		//iterate through the bombs
		for(x_iter = temp_result.ef_bombs.begin();
			x_iter != temp_result.ef_bombs.end();
			x_iter++)
		{
			//this finds the next bomb in the list, second condition should ALWAYS be true
			x_bomb = bombs.begin();
			while(x_bomb != bombs.end() && x_bomb->get_id() != *x_iter) {x_bomb++;}
			if(x_bomb != bombs.end() && x_bomb->get_id() != bomb.get_id()) // - safety to preven infinte recursion
			{
				

				bomb_explosion(*x_bomb, start_bomber);
			}
		}
		
		//add the explosion to the explosions vector
		Explosion temp_explosion(temp_result.explosion_data);
		explosions.push_back(temp_explosion);
	}

public:

	vector<Bomber> bombers;
	vector<Bomb> bombs;
	vector<PowerUp> powerups;
	vector<Explosion> explosions;

	struct team_dat
	{
		int id;
		int team_id;
	};

	int nPlayers;
	vector<char *> agent_file_names;
	vector<team_dat> team_data;







	/*
	The main function of the engine.
	A single time step is executed, performing:

	**for bombers:
	- bomb placement
	- detonation orders
	- movement / collision / bomb kicking
	**for bombs:
	- fuse-tick / detonation
	- movement / collision
	**for explosions:
	- display expiration

	If needed, enable the ascii dump at the end of this function.
	*/
	void execute_time_step()
	{
		Coordinates temp_coords;
		int temp_id;
		
		vector<Bomber>::iterator bomber;
		vector<Bomb>::iterator bomb;
		vector<PowerUp>::iterator powerup;
		vector<Explosion>::iterator explosion;
		//bool el_removed; //was an element removed from the vector this iteration

		VMInput vm_input;
		VMBomber temp_vm_bomber;
		VMBomb temp_vm_bomb;
		VMPowerUp temp_vm_pup;
		MoveResult temp_move_result;


		bomber = bombers.begin();
		for(bomber; bomber != bombers.end(); bomber++)
		{
			temp_vm_bomber.coords = bomber->coords;
			temp_vm_bomber.id = bomber->id;
			temp_vm_bomber.team_id = bomber->get_team_id();

			vm_input.vm_bombers.push_back(temp_vm_bomber);
		}
		
		bomb = bombs.begin();
		for(bomb; bomb != bombs.end(); bomb++)
		{
			temp_vm_bomb.coords = bomb->coords;
			temp_vm_bomb.is_callers = false;

			vm_input.vm_bombs.push_back(temp_vm_bomb);
		}

		powerup = powerups.begin();
		for(powerup; powerup != powerups.end(); powerup++)
		{
			temp_vm_pup.coords = powerup->coords;
			temp_vm_pup.type = powerup->power_type_index;

			vm_input.vm_powerups.push_back(temp_vm_pup);
		}

		vm_input.vm_tiles = new VMTile[stage.getH()*stage.getW()];
		for(unsigned int i = 0; i < stage.getW(); i++)
			for(unsigned int j = 0; j < stage.getH(); j++)
			{
				temp_coords.x = i;
				temp_coords.y = j;
				vm_input.vm_tiles[stage.unsafe_tile_index(temp_coords)].type = 
					(int)stage.tile_tinfo(temp_coords);
				
				//add unknown case
			}
		
		vm_input.H = stage.getH();
		vm_input.W = stage.getW();

		



		for(bomber = bombers.begin();
			bomber != bombers.end();
			bomber++)
		{
			if(bomber->is_alive() && !bomber->mstate.is_displaced())
			{
				vm_input.vm_bomber_status.position = bomber->coords;
				vm_input.vm_bomber_status.player_id = bomber->get_id();
				vm_input.vm_bomber_status.team_id = bomber->get_team_id();
				vm_input.vm_bomber_status.move_rate = bomber->prop.move_steps;
				vm_input.vm_bomber_status.has_kick = bomber->prop.kicking;
				vm_input.vm_bomber_status.has_detonate = bomber->prop.detonation;
				vm_input.vm_bomber_status.radius = bomber->bomb_prop.power;
				vm_input.vm_bomber_status.max_bombs = bomber->prop.max_bombs;
				vm_input.vm_bomber_status.deployed_bombs = bomber->nbombs;

				vm_input;
			}
				//MASON ******************** is now ready for passing to VM
		}

		/*************************** ignore this

		Here lies the virtual machine.

		****************************/







		bomber = bombers.begin();

		for(bomber; bomber != bombers.end(); bomber++)
		{
			if(bomber->is_alive()) //only do things if a bomber is alive
			{
				if(bomber->orders.move_ordered()) //movement orders
				{
					if(!(bomber->mstate.change_direction(bomber->orders.move_direction)))
					{}
				}
				if (bomber->orders.detonate) //detonation orders
				{
					if(bomber->prop.detonation)
					{
						temp_id = bomber->get_id();
						bomb = bombs.begin();
						while(bomb != bombs.end())
						{
							if(bomb->get_owner_id() == temp_id)
								bomb->orders.detonate = true;
							bomb++;
						}
					}
				}
				if (bomber->orders.bomb_place) //bomb placement orders
				{
					if(bomber->bombs_available())
					{
						temp_id = create_bomb_id();
						if(stage.place_bomb(*bomber,temp_id))
						{
							create_bomb(bomber->coords,bomber->id,bomber->bomb_prop,temp_id); //if the bomb is successfully placed, actually create it
							bomber->nbombs++;
						}
						else
						{} //nothing for now, maybe send a message back?
					}
					else
					{} //nothing for now, maybe send a message back?
				}


				
				/*
				The following performs movement (tile transition)
				based on the mstate (MoveState).
				*/
				
				if(bomber->mstate.is_displaced())
				{
					if(bomber->mstate.tick())  //did this 'tick' (time frame) cause a tile change?
					{
						temp_coords = bomber->mstate.dest(bomber->coords);
						bomber->mstate.perform_tile_change(); //updates the mstate, NOT coordinates
						stage.bomber_InformTileChange(bomber->get_id(),bomber->coords,temp_coords); //changes reservation data
						bomber->coords = temp_coords; //update coordinates
					}
					if(! bomber->mstate.is_displaced()) //if the last tick caused the bomber to be square on a tile
					{
						stage.bomber_InformTileClear(bomber->mstate.source(bomber->coords));
					}
				}
				else //bomber is sitting on a tile right now
				{
					if(bomber->mstate.moving()) //this unneccessarily checks displacement
					{
						temp_coords = bomber->mstate.dest(bomber->coords); //find the destination of movement
						temp_move_result = stage.bomber_CheckMove(temp_coords); // CHANGED
						if (temp_move_result.success()) //can a bomber move to the location?
						{
							stage.bomber_UnsafeCommenceMove(temp_coords); //perform neccessary reservations
							//the following code should be identical to the one above
							if(bomber->mstate.tick())  //did this 'tick' (time frame) cause a tile change?
							{
								temp_coords = bomber->mstate.dest(bomber->coords);
								bomber->mstate.perform_tile_change(); //updates the mstate, NOT coordinates
								stage.bomber_InformTileChange(bomber->get_id(),bomber->coords,temp_coords); //changes reservation data
								bomber->coords = temp_coords; //update coordinates
							}
							if(! bomber->mstate.is_displaced()) //if the last tick caused the bomber to be square on a tile
							{
								stage.bomber_InformTileClear(bomber->mstate.source(bomber->coords));
							}
						}
						else if (temp_move_result.get_type() == mrtBOMB)
						{
							if(bomber->prop.kicking) //can the bomber "kick" bombs?
							{
								if(bombs.size() > 0)
								{
									bomb = bombs.begin();
									while(bomb != bombs.end())
									{
										if(bomb->get_id() == temp_move_result.get_collidee_id())
											break;
										else
											bomb++;
									}

									//bomb now points to the bomb to be kicked
									if(bomb->mstate.is_displaced())
										stage.bomb_InformTileClear(bomb->mstate.reserved(bomb->coords));
									 //if bomb isn't already kicked in that direction
									if(bomb->mstate.get_direction() != bomber->mstate.get_direction())
										bomb->mstate.reset(bomber->mstate.get_direction()); //the bomb is now centered on the tile and kicked

								}
								// there is no else - it would be a bug
								

							}
						}
					}
				}


				//check if current tile has a powerup, give it to bomber
				temp_id = stage.acquire_powerup(bomber->coords);
				if(temp_id >= 0) //an actual PowerUp acquired
				{
					powerup = powerups.begin();
					while( powerup != powerups.end())
					{
						if(powerup->id == temp_id)
						{
							bomber->gain_powerup(powerup->power_type_index);
							powerup = powerups.erase(powerup);
							continue;
						}
						powerup++;
					}
				}


			} //end if( is alive)
		}




		//bombs
		bomb = bombs.begin();
		while(bomb != bombs.end())
		{
			//gone is equivalent to is_alive for bombers
			//see definition of bomb
			//actions only performed if bomb isn't 'gone'
			if(!bomb->gone)
			{
			if (bomb->orders.detonate || bomb->tick()) //did this bomb tick cause the bomb to detonate, was there an order?
			{
				bomber = bombers.begin();
				while(bomber != bombers.end() && bomber->get_id() != bomb->get_owner_id()) bomber++;

				if(bomber != bombers.end())
				{
					bomb_explosion(*bomb, bomber);
				}
			}
			else //if bomb still exists, do its mstate tick (mstate is movement state)
			{
				if(bomb->mstate.is_displaced())
				{
					if(bomb->mstate.tick())  //did this 'tick' (time frame) cause a tile change?
					{
						temp_coords = bomb->mstate.dest(bomb->coords);
						bomb->mstate.perform_tile_change(); //updates the mstate, NOT coordinates
						stage.bomb_InformTileChange(bomb->get_id(),bomb->coords,temp_coords); //changes reservation data
						bomb->coords = temp_coords; //update coordinates
					}
					if(! bomb->mstate.is_displaced()) //if the last tick caused the bomb to be square on a tile
					{
						stage.bomb_InformTileClear(bomb->mstate.source(bomb->coords));
					}
				}
				else //bomb is sitting on a tile right now
				{
					if(bomb->mstate.moving()) //this unneccessarily checks displacement
					{
						temp_coords = bomb->mstate.dest(bomb->coords); //find the destination of movement
						if (stage.bomb_CheckMove(temp_coords).success()) //can a bomb move to the location?
						{
							stage.bomb_UnsafeCommenceMove(temp_coords); //perform neccessary reservations
							//the following code should be identical to the one above
							if(bomb->mstate.tick())  //did this 'tick' (time frame) cause a tile change?
							{
								temp_coords = bomb->mstate.dest(bomb->coords);
								bomb->mstate.perform_tile_change(); //updates the mstate, NOT coordinates
								stage.bomb_InformTileChange(bomb->get_id(),bomb->coords,temp_coords); //changes reservation data
								bomb->coords = temp_coords; //update coordinates
							}
							if(! bomb->mstate.is_displaced()) //if the last tick caused the bomb to be square on a tile
							{
								stage.bomb_InformTileClear(bomb->mstate.source(bomb->coords));
							}
						}
						else
						{
							//the bomb collided with something, stop its movement
							bomb->mstate.change_direction(dVOID);
						}
					}
				}
			}
			} //end if(!bomb->gone)
			bomb++;
		}
		
		//the following while loop removes any bombs not initially removed
		bomb = bombs.begin();
		while(bomb != bombs.end())
		{
			if(bomb->gone)
			{
				//removes indecies and reservations from stage
				stage.remove_bomb_residue(bomb->coords);
				if(bomb->mstate.is_displaced())
					stage.bomb_InformTileClear(bomb->mstate.reserved(bomb->coords));
				bomb = bombs.erase(bomb);
			}
			else
				bomb++;
		}

		
		//nothing needs to be done for powerups

		
		//explosion expiration
		explosion = explosions.begin();
		while(explosion != explosions.end())
		{
			//if explosion expired, remove its 'residue' from tiles
			if(explosion->tick())
			{
				for(unsigned int i = 1; i <= explosion->explosion_data.dW; i++)
				{
					temp_coords.x = explosion->explosion_data.center.x - i;
					temp_coords.y = explosion->explosion_data.center.y;
					stage.clear_explosion(temp_coords);
				}
					temp_coords.x = explosion->explosion_data.center.x - (explosion->explosion_data.dW + 1);
					temp_coords.y = explosion->explosion_data.center.y;
					stage.clear_explosion_brick(temp_coords);

				for(unsigned int i = 1; i <= explosion->explosion_data.dE; i++)
				{
					temp_coords.x = explosion->explosion_data.center.x + i;
					temp_coords.y = explosion->explosion_data.center.y;
					stage.clear_explosion(temp_coords);
				}
					temp_coords.x = explosion->explosion_data.center.x + (explosion->explosion_data.dE + 1);
					temp_coords.y = explosion->explosion_data.center.y;
					stage.clear_explosion_brick(temp_coords);

				for(unsigned int i = 1; i <= explosion->explosion_data.dS; i++)
				{
					temp_coords.x = explosion->explosion_data.center.x;
					temp_coords.y = explosion->explosion_data.center.y + i;
					stage.clear_explosion(temp_coords);
				}
					temp_coords.x = explosion->explosion_data.center.x;
					temp_coords.y = explosion->explosion_data.center.y + (explosion->explosion_data.dS + 1);
					stage.clear_explosion_brick(temp_coords);

				for(unsigned int i = 1; i <= explosion->explosion_data.dN; i++)
				{
					temp_coords.x = explosion->explosion_data.center.x;
					temp_coords.y = explosion->explosion_data.center.y - i;
					stage.clear_explosion(temp_coords);
				}
					temp_coords.x = explosion->explosion_data.center.x;
					temp_coords.y = explosion->explosion_data.center.y - (explosion->explosion_data.dN + 1);
					stage.clear_explosion_brick(temp_coords);

				explosion = explosions.erase(explosion);
				continue; //to avoid increment
			}
			explosion++;
		}
		//stage.ascii_dump(); //testing code

	}//end of execute_time_step

	//gets type of tile at i,j
	int get_tile_tindex(unsigned int& i, unsigned int& j)
	{
		Coordinates temp(i,j);
		return stage.tile_tinfo(temp);
	}

	//checks if type of tile at i,j is tindex
	bool check_tile_index(unsigned int& i, unsigned int& j, int tindex)
	{
		Coordinates temp(i,j);
		return stage.check_tinfo(temp,tindex);
	}

	unsigned int getHeight(){return stage.getH();}
	unsigned int getWidth(){return stage.getW();}





	/*
	file:

	nPlayers
	<agent file name> <team number>
	<agent file name> <team number>
	...
	*/
	void init_player_data()
	{
		fstream fin(fname_PLAYER_DATA);
		char * temp_agent_file = 0;
		int temp_team_id;
		team_dat temp_team_dat;

		if(!fin.eof())
			fin >> nPlayers;

		for(int i = 0; i < nPlayers; i++)
		{
			if(!fin.eof())
			{
				fin >> temp_agent_file;
				fin >> temp_team_id;
				
				temp_team_dat.id = i;
				temp_team_dat.team_id = temp_team_id;

				agent_file_names.push_back(temp_agent_file);
				team_data.push_back(temp_team_dat);
			}
			else
				break;
		}


		
		fin.close();
		/*
			MASON **********************************************
			Here team_data, agent_file_name should be sent to the VM
		*/
	}



	/*
	Initialization function. Initializes the stage.
	*/
	void init()
	{
		init_player_data(); //get number of players

		vector<Coordinates> player_coords;
		vector<Coordinates>::iterator i_pcoords;

		player_coords = stage.init(); //send number of players
		i_pcoords = player_coords.begin();

		for(int i = 0; i_pcoords != player_coords.end(); i++,i_pcoords++)
		{
			Bomber temp_bomber(i);
			temp_bomber.coords = *i_pcoords;
			bombers.push_back(temp_bomber);
			stage.place_bomber(temp_bomber);
		}
	}
};

#endif //BMD_ENGINE_H